package com.mgy.im.standard;

import com.mgy.im.autoconfigure.NettyWebSocketProperties;
import com.mgy.im.constants.Constant;
import com.mgy.im.util.IpUtil;
import com.mgy.im.util.NetUtil;
import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.*;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.channel.socket.nio.NioSocketChannel;
import io.netty.handler.codec.http.HttpObjectAggregator;
import io.netty.handler.codec.http.HttpServerCodec;
import io.netty.handler.codec.http.websocketx.WebSocketFrameAggregator;
import io.netty.handler.codec.http.websocketx.extensions.compression.WebSocketServerCompressionHandler;
import io.netty.handler.logging.LogLevel;
import io.netty.handler.logging.LoggingHandler;
import io.netty.handler.stream.ChunkedWriteHandler;
import io.netty.handler.timeout.IdleStateHandler;
import lombok.extern.slf4j.Slf4j;
import org.springframework.util.StringUtils;

import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.UnknownHostException;

/**
 * xxx
 *
 * @author maguoyong
 * @date 2020/6/14
 */
@Slf4j
public class WebsocketServer {
    private NettyWebSocketProperties properties;
    private WebSocketHandler webSocketHandler;

    public WebsocketServer(NettyWebSocketProperties properties, WebSocketHandler webSocketHandler) {
        this.properties = properties;
        this.webSocketHandler = webSocketHandler;
    }

    public void start() {
        EventLoopGroup boss = new NioEventLoopGroup(properties.getBossLoopGroupThreads());
        EventLoopGroup worker = new NioEventLoopGroup(properties.getWorkerLoopGroupThreads());
        ServerBootstrap bootstrap = new ServerBootstrap();
        bootstrap.group(boss, worker)
                .channel(NioServerSocketChannel.class)
                .option(ChannelOption.CONNECT_TIMEOUT_MILLIS, properties.getConnectTimeoutMillis())
                .option(ChannelOption.SO_BACKLOG, properties.getSoBacklog())
                .childOption(ChannelOption.WRITE_SPIN_COUNT, properties.getWriteSpinCount())
                .childOption(ChannelOption.WRITE_BUFFER_WATER_MARK, new WriteBufferWaterMark(properties.getWriteBufferLowWaterMark(), properties.getWriteBufferHighWaterMark()))
                .childOption(ChannelOption.TCP_NODELAY, properties.isTcpNodelay())
                .childOption(ChannelOption.SO_KEEPALIVE, properties.isSoKeepalive())
                .childOption(ChannelOption.SO_LINGER, properties.getSoLinger())
                .childOption(ChannelOption.ALLOW_HALF_CLOSURE, properties.isAllowHalfClosure())
                .handler(new LoggingHandler(LogLevel.DEBUG))
                .childHandler(new ChannelInitializer<NioSocketChannel>() {
                    @Override
                    protected void initChannel(NioSocketChannel ch) throws Exception {
                        ChannelPipeline pipeline = ch.pipeline();
                        //处理日志
                        pipeline.addLast(new LoggingHandler(LogLevel.INFO));

                        //处理心跳
                        if (properties.getReaderIdleTimeSeconds() != 0 || properties.getWriterIdleTimeSeconds() != 0 || properties.getAllIdleTimeSeconds() != 0) {
                            pipeline.addLast(new IdleStateHandler(properties.getReaderIdleTimeSeconds(), properties.getWriterIdleTimeSeconds(), properties.getAllIdleTimeSeconds()));
                        }
                        pipeline.addLast(new HeartbeatHandler());
                        //websocket协议本身是基于http协议的，所以这边也要使用http解编码器
                        pipeline.addLast(new HttpServerCodec());
                        pipeline.addLast(new ChunkedWriteHandler());
                        pipeline.addLast(new HttpObjectAggregator(64 * 1024));
                        pipeline.addLast(new HttpRequestHandler(properties, webSocketHandler));
//                        pipeline.addLast(new WebSocketServerProtocolHandler(properties.getPath()));
                        pipeline.addLast(new TextWebSocketFrameHandler(properties, webSocketHandler));


                        if (properties.isUseCompressionHandler()) {
                            pipeline.addLast(new WebSocketServerCompressionHandler());
                        }
                        pipeline.addLast(new WebSocketFrameAggregator(Integer.MAX_VALUE));
                    }
                });

        if (properties.getSoRcvbuf() != -1) {
            bootstrap.childOption(ChannelOption.SO_RCVBUF, properties.getSoRcvbuf());
        }

        if (properties.getSoSndbuf() != -1) {
            bootstrap.childOption(ChannelOption.SO_SNDBUF, properties.getSoSndbuf());
        }

        ChannelFuture channelFuture;
        String ip = getIp(properties.getHost());
        Integer port = getPort(properties.getPort());
        System.out.println(String.format("netty-websocket-server地址：%s:%s", ip, port));
        if (IpUtil.ANYHOST.equals(ip)) {
            channelFuture = bootstrap.bind(port);
        } else {
            try {
                channelFuture = bootstrap.bind(new InetSocketAddress(InetAddress.getByName(ip), port));
            } catch (UnknownHostException e) {
                channelFuture = bootstrap.bind(ip, port);
                e.printStackTrace();
            }
        }

        channelFuture.addListener(future -> {
            if (!future.isSuccess()) {
                future.cause().printStackTrace();
            }
        });

        Runtime.getRuntime().addShutdownHook(new Thread(() -> {
            boss.shutdownGracefully().syncUninterruptibly();
            worker.shutdownGracefully().syncUninterruptibly();
        }));
    }

    private String getIp(String host) {
        if (StringUtils.hasText(host)) {
            return host;
        }
        String ip = System.getProperty(Constant.NETTY_WEBSOCKET_SERVER_IP);
        if (StringUtils.hasText(ip)) {
            return ip;
        }
        return IpUtil.getIp();
    }

    private int getPort(Integer port) {
        if (port != null && port > 0) {
            return port;
        }
        String portStr = System.getProperty(Constant.NETTY_WEBSOCKET_SERVER_PORT);
        port = StringUtils.hasText(portStr) ? Integer.valueOf(portStr) : null;
        if (port != null && port > 0) {
            return port;
        }
        return NetUtil.findAvailablePort(8086);
    }
}
